package org.expressme.ant;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

import org.apache.tools.ant.BuildException;

/**
 * Generate mx:RemoteObject definition from Java interface.
 * 
 * @author Michael Liao (askxuefeng@gmail.com)
 */
public class JavaInterface2RemoteObject {

    private String handlerSuffix = "Handler";
    private String faultSuffix = "Fault";

    public String toRemoteObjectString(
            Class<?> clazz,
            String destination,
            String concurrency,
            boolean makeObjectsBindable,
            boolean showBusyCursor,
            boolean orderByName
    ) throws BuildException {
        Method[] methods = clazz.getMethods();
        if (orderByName) {
            Arrays.sort(
                    methods,
                    new Comparator<Method>() {
                        public int compare(Method m1, Method m2) {
                            return m1.getName().compareTo(m2.getName());
                        }
                    }
            );
        }
        String s = Utils.loadResource("org/expressme/ant/Interface.as");
        String m = Utils.loadResource("org/expressme/ant/InterfaceMethod.as");
        s = s.replace("${PACKAGE}", clazz.getPackage().getName());
        s = s.replace("${CLASS}", clazz.getSimpleName() + "RO");
        s = s.replace("${DESTINATION}", destination);
        s = s.replace("${CONCURRENCY}", concurrency);
        s = s.replace("${MAKE_OBJECTS_BINDABLE}", String.valueOf(makeObjectsBindable));
        s = s.replace("${SHOW_BUSY_CURSOR}", String.valueOf(showBusyCursor));
        Set<String> imports = new HashSet<String>();
        StringBuilder sb = new StringBuilder(4096);
        for (Method method : methods) {
            sb.append(generateMethod(m, method, imports));
        }
        s = s.replace("${METHODS}", sb.toString());
        // build imports:
        String[] ss = imports.toArray(new String[imports.size()]);
        Arrays.sort(ss);
        sb = new StringBuilder(4096);
        for (String t : ss) {
            sb.append("    import ").append(t).append(";\r\n");
        }
        return s.replace("${IMPORTS}", sb.toString());
    }

    String generateMethod(String m, Method method, Set<String> imports) {
        m = m.replace("${METHOD}", method.getName());
        StringBuilder sb = new StringBuilder(64);
        Class<?>[] params = method.getParameterTypes();
        int n = 1;
        for (Class<?> param : params) {
            String asType = param.isArray() ? "Array" : Utils.getMapping(param);
            if (asType==null) {
                imports.add(param.getName());
                asType = param.getSimpleName();
            }
            if (asType==null)
                throw new BuildException("Cannot mapping type '" + param.getName() + "' to ActionScript.");
            sb.append("arg").append(n).append(" : ").append(asType).append(", ");
            n++;
        }
        StringBuilder arg = new StringBuilder();
        for (int i=1; i<=params.length; i++) {
            arg.append("arg").append(i);
            if (i<params.length)
                arg.append(", ");
        }
        m = m.replace("${ARGS}", arg.toString());
        return m.replace("${METHOD_PARAMETERS}", sb.toString());
    }

    String getRemoteObjectId(Class<?> clazz, String remoteObjectId) {
        if (remoteObjectId==null || remoteObjectId.trim().isEmpty())
            return Character.toLowerCase(clazz.getSimpleName().charAt(0)) + clazz.getSimpleName().substring(1) + "RO";
        return remoteObjectId.trim();
    }

    public String getHandler(Method method) {
        return method.getName() + handlerSuffix;
    }

    public String getFault(Method method) {
        return method.getName() + faultSuffix;
    }

    public void setHandlerSuffix(String handlerSuffix) {
        this.handlerSuffix = handlerSuffix;
    }

    public void setFaultSuffix(String faultSuffix) {
        this.faultSuffix = faultSuffix;
    }

}
